/*******************************************************************************
 * Copyright (c) 2018, 2019 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package boost.runtimes.openliberty;

import static boost.common.config.ConfigConstants.*;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

import boost.common.BoostLoggerI;
import boost.common.config.BoostProperties;
import boost.common.utils.BoostUtil;

/**
 * Create a Liberty server.xml
 *
 */
public class LibertyServerConfigGenerator {

    private final String serverPath;
    private final String libertyInstallPath;

    private final BoostLoggerI logger;

    private Document serverXml;
    private Element featureManager;
    private Element serverRoot;
    private Element httpEndpoint;

    private Set<String> featuresAdded;

    private Properties bootstrapProperties;

    private final Properties boostConfigProperties;

    public LibertyServerConfigGenerator(String serverPath, BoostLoggerI logger) throws ParserConfigurationException {

        this.serverPath = serverPath;
        this.libertyInstallPath = serverPath + "/../../.."; // Three directories
                                                            // back from
                                                            // 'wlp/usr/servers/BoostServer'
        this.logger = logger;

        boostConfigProperties = BoostProperties.getConfiguredBoostProperties(logger);

        generateServerXml();

        featuresAdded = new HashSet<String>();
        bootstrapProperties = new Properties();
    }

    private void generateServerXml() throws ParserConfigurationException {
        DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();

        // Create top level server config element
        serverXml = docBuilder.newDocument();
        serverRoot = serverXml.createElement("server");
        serverRoot.setAttribute("description", "Liberty server generated by Liberty Boost");
        serverXml.appendChild(serverRoot);

        // Create featureManager config element
        featureManager = serverXml.createElement(FEATURE_MANAGER);
        serverRoot.appendChild(featureManager);

        // Create httpEndpoint config element
        httpEndpoint = serverXml.createElement(HTTP_ENDPOINT);
        httpEndpoint.setAttribute("id", DEFAULT_HTTP_ENDPOINT);
        serverRoot.appendChild(httpEndpoint);
    }

    /**
     * Add a Liberty feature to the server configuration
     *
     */
    public void addFeature(String featureName) {
        if (!featuresAdded.contains(featureName)) {
            Element feature = serverXml.createElement(FEATURE);
            feature.appendChild(serverXml.createTextNode(featureName));
            featureManager.appendChild(feature);
            featuresAdded.add(featureName);
        }
    }

    /**
     * Add a list of features to the server configuration
     *
     */
    public void addFeatures(List<String> features) {

        for (String featureName : features) {
            addFeature(featureName);
        }
    }

    /**
     * Write the server.xml and bootstrap.properties to the server config
     * directory
     *
     * @throws TransformerException
     * @throws IOException
     */
    public void writeToServer() throws TransformerException, IOException {
        // Replace auto-generated server.xml
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        Transformer transformer = transformerFactory.newTransformer();
        transformer.setOutputProperty(OutputKeys.METHOD, "xml");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        DOMSource source = new DOMSource(serverXml);
        StreamResult result = new StreamResult(new File(serverPath + "/server.xml"));
        transformer.transform(source, result);

        // Generate bootstrap.properties
        if (!bootstrapProperties.isEmpty()) {

            OutputStream output = null;
            try {
                output = new FileOutputStream(serverPath + "/bootstrap.properties");
                bootstrapProperties.store(output, null);
            } finally {
                if (output != null) {
                    output.close();
                }
            }
        }

        // Try setting all bootstrap properties to system properties for test
        // purposes
        for (String key : bootstrapProperties.stringPropertyNames()) {
            System.setProperty(key, bootstrapProperties.getProperty(key));
        }
    }

    public void addBootstrapProperties(Properties properties) throws IOException {

        if (properties != null) {
            for (String key : properties.stringPropertyNames()) {
                String value = properties.getProperty(key);

                addBoostrapProperty(key, value);
            }
        }
    }

    private void addBoostrapProperty(String key, String value) throws IOException {

        // Using this to hold the properties we want to encrypt and the type of
        // encryption we want to use
        Map<String, String> propertiesToEncrypt = BoostProperties.getPropertiesToEncrypt();

        if (propertiesToEncrypt.containsKey(key) && value != null && !value.equals("")) {
            // Getting properties that might not have been passed with the other
            // properties that will be written to boostrap.properties
            // Don't want to add certain properties to the boostrap properties
            // so we'll grab them here
            Properties supportedProperties = BoostProperties.getConfiguredBoostProperties(logger);
            value = BoostUtil.encrypt(libertyInstallPath, value, propertiesToEncrypt.get(key),
                    supportedProperties.getProperty(BoostProperties.AES_ENCRYPTION_KEY), logger);
        }

        bootstrapProperties.put(key, value);
    }

    public void addKeystore(Map<String, String> keystoreProps, Map<String, String> keyProps) {
        Element keystore = serverXml.createElement(KEYSTORE);
        keystore.setAttribute("id", DEFAULT_KEYSTORE);

        for (String key : keystoreProps.keySet()) {
            keystore.setAttribute(key, keystoreProps.get(key));
        }

        if (!keyProps.isEmpty()) {
            Element keyEntry = serverXml.createElement(KEY_ENTRY);

            for (String key : keyProps.keySet()) {
                keyEntry.setAttribute(key, keyProps.get(key));
            }

            keystore.appendChild(keyEntry);
        }

        serverRoot.appendChild(keystore);
    }

    public void addApplication(String appName) {
        Element appCfg = serverXml.createElement(APPLICATION);
        appCfg.setAttribute(CONTEXT_ROOT, "/");
        appCfg.setAttribute(LOCATION, appName + "." + WAR_PKG_TYPE);
        appCfg.setAttribute(TYPE, WAR_PKG_TYPE);
        serverRoot.appendChild(appCfg);

    }

    public void addHostname(String hostname) throws Exception {
        httpEndpoint.setAttribute("host", BoostUtil.makeVariable(BoostProperties.ENDPOINT_HOST));

        addBoostrapProperty(BoostProperties.ENDPOINT_HOST, hostname);
    }

    public void addHttpPort(String httpPort) throws Exception {
        httpEndpoint.setAttribute("httpPort", BoostUtil.makeVariable(BoostProperties.ENDPOINT_HTTP_PORT));

        addBoostrapProperty(BoostProperties.ENDPOINT_HTTP_PORT, httpPort);
    }

    public void addHttpsPort(String httpsPort) throws Exception {
        httpEndpoint.setAttribute("httpsPort", BoostUtil.makeVariable(BoostProperties.ENDPOINT_HTTPS_PORT));

        addBoostrapProperty(BoostProperties.ENDPOINT_HTTPS_PORT, httpsPort);
    }

    public Document getServerXmlDoc() {
        return serverXml;
    }
}
